import { NativeEventEmitter, NativeModules } from "react-native";
import {
  BleScanCallbackType,
  BleScanMatchCount,
  BleScanMatchMode,
  BleScanMode,
  BleState,
  ConnectOptions,
  ConnectionPriority,
  CompanionScanOptions,
  Peripheral,
  PeripheralInfo,
  ScanOptions,
  StartOptions,
} from "./types";

export * from "./types";
import ReactNativeBleManager from './NativeGeneratedBleManagerTurboModule'

let bleManager = ReactNativeBleManager;

class BleManager extends NativeEventEmitter {
  constructor() {
    // @ts-ignore
    super(bleManager);
    this.isPeripheralConnected = this.isPeripheralConnected.bind(this);
  }

  /**
   *
   * @param peripheralId
   * @param serviceUUID
   * @param characteristicUUID
   * @returns data as an array of numbers (which can be converted back to a Uint8Array (ByteArray) using something like [Buffer.from()](https://github.com/feross/buffer))
   */
  read(peripheralId: string, serviceUUID: string, characteristicUUID: string) {
    return new Promise<number[]>((fulfill, reject) => {
      bleManager.read(
        peripheralId,
        serviceUUID,
        characteristicUUID,
      ).then((data: number[]) => {
        fulfill(data);
      }).catch((error) => {
        reject(error);
      });
    })
  }

  /**
   *
   * @param peripheralId
   * @param serviceUUID
   * @param characteristicUUID
   * @param descriptorUUID
   * @returns data as an array of numbers (which can be converted back to a Uint8Array (ByteArray) using something like [Buffer.from()](https://github.com/feross/buffer))
   */
  readDescriptor(
    peripheralId: string,
    serviceUUID: string,
    characteristicUUID: string,
    descriptorUUID: string
  ) {
    return new Promise<number[]>((fulfill, reject) => {
      bleManager.readDescriptor(
        peripheralId,
        serviceUUID,
        characteristicUUID,
        descriptorUUID,
      ).then((data: number[]) => {
        fulfill(data);
      }).catch((error) => {
        reject(error);
      });
    });
  }

  /**
   *
   * @param peripheralId
   * @param serviceUUID
   * @param characteristicUUID
   * @param descriptorUUID
   * @param data data to write as an array of numbers (which can be converted from a Uint8Array (ByteArray) using something like [Buffer.toJSON().data](https://github.com/feross/buffer))
   * @returns
   */
  writeDescriptor(
    peripheralId: string,
    serviceUUID: string,
    characteristicUUID: string,
    descriptorUUID: string,
    data: number[]
  ) {
    return new Promise<void>((fulfill, reject) => {
      bleManager.writeDescriptor(
        peripheralId,
        serviceUUID,
        characteristicUUID,
        descriptorUUID,
        data,
      ).then(() => {
        fulfill();
      }).catch((error) => {
        reject(error);
      });
    });
  }

  /**
   *
   * @param peripheralId
   * @returns a promise resolving with the updated RSSI (`number`) if it succeeds.
   */
  readRSSI(peripheralId: string) {
    return new Promise<number>((fulfill, reject) => {
      bleManager.readRSSI(
        peripheralId,
      ).then((rssi: number) => {
        fulfill(rssi);
      }).catch((error) => {
        reject(error);
      });
    });
  }

  /**
   * [Android only]
   * @param peripheralId
   * @returns a promise that resolves to a boolean indicating if gatt was successfully refreshed or not.
   */
  refreshCache(peripheralId: string) {
    return new Promise<boolean>((fulfill, reject) => {
      bleManager.refreshCache(
        peripheralId
      ).then(() => {
        console.log('harmony is not support')
      }).catch((error) => {
        reject(error)
      });
    });
  }

  /**
   *
   * @param peripheralId
   * @param serviceUUIDs [iOS only] optional filter of services to retrieve.
   * @returns
   */
  retrieveServices(peripheralId: string, serviceUUIDs: string[] = []) {
    return new Promise<PeripheralInfo>((fulfill, reject) => {
      bleManager.retrieveServices(
        peripheralId,
        serviceUUIDs,
      ).then((peripheral: PeripheralInfo) => {
        fulfill(peripheral);
      }).catch((error) => {
        reject(error);
      });
    })
  }

  /**
   *
   * @param peripheralId
   * @param serviceUUID
   * @param characteristicUUID
   * @param data data to write as an array of numbers (which can be converted from a Uint8Array (ByteArray) using something like [Buffer.toJSON().data](https://github.com/feross/buffer))
   * @param maxByteSize optional, defaults to 20
   * @returns
   */
  write(
      peripheralId: string,
      serviceUUID: string,
      characteristicUUID: string,
      data: number[],
      maxByteSize: number = 20
    ) {
      return new Promise<void>((fulfill, reject) => {
        bleManager.write(
          peripheralId,
          serviceUUID,
          characteristicUUID,
          data,
          maxByteSize,
        ).then(() => {
          fulfill();
        }).catch((error)=>{
          reject(error);
        });
      });
    }

  /**
   *
   * @param peripheralId
   * @param serviceUUID
   * @param characteristicUUID
   * @param data data to write as an array of numbers (which can be converted from a Uint8Array (ByteArray) using something like [Buffer.toJSON().data](https://github.com/feross/buffer))
   * @param maxByteSize optional, defaults to 20
   * @param queueSleepTime optional, defaults to 10. Only useful if data length is greater than maxByteSize.
   * @returns
   */
  writeWithoutResponse(
      peripheralId: string,
      serviceUUID: string,
      characteristicUUID: string,
      data: number[],
      maxByteSize: number = 20,
      queueSleepTime: number = 10
    ) {
      return new Promise<void>((fulfill, reject) => {
        bleManager.writeWithoutResponse(
          peripheralId,
          serviceUUID,
          characteristicUUID,
          data,
          maxByteSize,
          queueSleepTime,
        ).then(() => {
          console.log('harmony is not support')
        }).catch((error)=>{
          reject(error);
        });
      });
    }

  connect(peripheralId: string, options ?: ConnectOptions) {
      return new Promise<void>((fulfill, reject) => {
        if (!options) {
          options = {};
        }
        bleManager.connect(peripheralId, options).then(() => {
          fulfill();
        }).catch((error)=>{
          reject(error);
        });
      });
    }

  /**
   * [Android only]
   * @param peripheralId
   * @param peripheralPin optional. will be used to auto-bond if possible.
   * @returns
   */
  createBond(peripheralId: string, peripheralPin: string | null = null) {
      return new Promise<void>((fulfill, reject) => {
        bleManager.createBond(
          peripheralId,
          peripheralPin,
        ).then(() => {
          fulfill();
        }).catch((error)=>{
          reject(error);
        });
      });
    }

  /**
   * [Android only]
   * @param peripheralId
   * @returns
   */
  removeBond(peripheralId: string) {
      return new Promise<void>((fulfill, reject) => {
        bleManager.removeBond(peripheralId).then(() => {
          fulfill();
        }).catch((error)=>{
          reject(error);
        });
      });
    }

  /**
   *
   * @param peripheralId
   * @param force [Android only] defaults to true.
   * @returns
   */
  disconnect(peripheralId: string, force: boolean = true) {
      return new Promise<void>((fulfill, reject) => {
        bleManager.disconnect(peripheralId, force).then(() => {
          fulfill();
        }).catch((error)=>{
          reject(error);
        });
      });
    }

  startNotification(
      peripheralId: string,
      serviceUUID: string,
      characteristicUUID: string
    ) {
      return new Promise<void>((fulfill, reject) => {
        bleManager.startNotification(
          peripheralId,
          serviceUUID,
          characteristicUUID,
        ).then(() => {
          fulfill();
        }).catch((error)=>{
          reject(error);
        });
      });
    }

  /**
   * [Android only]
   * @param peripheralId
   * @param serviceUUID
   * @param characteristicUUID
   * @param buffer
   * @returns
   */
  startNotificationUseBuffer(
      peripheralId: string,
      serviceUUID: string,
      characteristicUUID: string,
      buffer: number
    ) {
      return new Promise<void>((fulfill, reject) => {
        bleManager.startNotificationUseBuffer(
          peripheralId,
          serviceUUID,
          characteristicUUID,
          buffer
        ).then(() => {
          console.log('harmony is not support')
        }).catch((error)=>{
          reject(error)
        });
      });
    }

  stopNotification(
      peripheralId: string,
      serviceUUID: string,
      characteristicUUID: string
    ) {
      return new Promise<void>((fulfill, reject) => {
        bleManager.stopNotification(
          peripheralId,
          serviceUUID,
          characteristicUUID
        ).then(() => {
          console.log('harmony is not support')
        }).catch((error)=>{
          reject(error)
        });
      });
    }

  checkState() {
      return new Promise<BleState>((fulfill, _) => {
        bleManager.checkState().then((state: BleState) => {
          fulfill(state);
        });
      });
    }

  start(options ?: StartOptions) {
      return new Promise<void>((fulfill, reject) => {
        if (options == null) {
          options = {};
        }
        bleManager.start(options).then(() => {
          fulfill();
        }).catch((error)=>{
          reject(error);
        });
      });
    }

  /**
   *
   * @param serviceUUIDs
   * @param seconds amount of seconds to scan. if set to 0 or less, will scan until you call stopScan() or the OS stops the scan (background etc).
   * @param allowDuplicates [iOS only]
   * @param scanningOptions optional map of properties to fine-tune scan behavior, see DOCS.
   * @returns
   */
  scan(
      serviceUUIDs: string[],
      seconds: number,
      allowDuplicates ?: boolean,
      scanningOptions: ScanOptions = {}
    ) {
      return new Promise<void>((fulfill, reject) => {
        if (allowDuplicates == null) {
          allowDuplicates = false;
        }

        // (ANDROID) Match as many advertisement per filter as hw could allow
        // depends on current capability and availability of the resources in hw.
        if (scanningOptions.numberOfMatches == null) {
          scanningOptions.numberOfMatches = BleScanMatchCount.MaxAdvertisements;
        }

        // (ANDROID) Defaults to MATCH_MODE_AGGRESSIVE
        if (scanningOptions.matchMode == null) {
          scanningOptions.matchMode = BleScanMatchMode.Aggressive;
        }

        // (ANDROID) Defaults to SCAN_MODE_LOW_POWER
        if (scanningOptions.scanMode == null) {
          scanningOptions.scanMode = BleScanMode.LowPower;
        }

        // (ANDROID) Defaults to CALLBACK_TYPE_ALL_MATCHES
        // WARN: sometimes, setting a scanSetting instead of leaving it untouched might result in unexpected behaviors.
        // https://github.com/dariuszseweryn/RxAndroidBle/issues/462
        if (scanningOptions.callbackType == null) {
          scanningOptions.callbackType = BleScanCallbackType.AllMatches;
        }

        // (ANDROID) Defaults to 0ms (report results immediately).
        if (scanningOptions.reportDelay == null) {
          scanningOptions.reportDelay = 0;
        }

        // In Android ScanFilter used to restrict search to devices with a specific advertising name.
        // https://developer.android.com/reference/android/bluetooth/le/ScanFilter.Builder#setDeviceName(java.lang.String)
        // In iOS, this is a whole word match, not a partial search.
        if (!scanningOptions.exactAdvertisingName) {
          delete scanningOptions.exactAdvertisingName;
        } else {
          if (typeof scanningOptions.exactAdvertisingName === "string") {
            scanningOptions.exactAdvertisingName = [
              scanningOptions.exactAdvertisingName,
            ];
          }
        }

        bleManager.scan(
          serviceUUIDs,
          seconds,
          allowDuplicates,
          scanningOptions,
        ).then(() => {
          fulfill();
        }).catch((error)=>{
          reject(error);
        });
      });
    }

  stopScan() {
      return new Promise<void>((fulfill, reject) => {
        bleManager.stopScan().then(() => {
          fulfill();
        }).catch((error)=>{
          reject(error);
        });
      });
    }

  /**
   * [Android only] triggers an ENABLE_REQUEST intent to the end-user to enable bluetooth.
   * @returns
   */
  enableBluetooth() {
      return new Promise<void>((fulfill, reject) => {
        bleManager.enableBluetooth().then(() => {
          fulfill();
        }).catch((error)=>{
          reject(error);
        });
      });
    }

  /**
   *
   * @param serviceUUIDs [optional] not used on android, optional on ios.
   * @returns
   */
  getConnectedPeripherals(serviceUUIDs: string[] = []) {
      return new Promise<Peripheral[]>((fulfill, reject) => {
        bleManager.getConnectedPeripherals(
          serviceUUIDs,
        ).then((result: Peripheral[]) => {
          fulfill(result)
        }).catch((error) => {
          reject(error)
        });
      });
    }

  /**
   * [Android only]
   * @returns
   */
  getBondedPeripherals() {
      return new Promise<Peripheral[]>((fulfill, reject) => {
        bleManager.getBondedPeripherals().then((result: Peripheral[] | null) => {
          if (result) {
            fulfill(result);
          } else {
            fulfill([]);
          }
        }).catch((error)=>{
          reject(error);
        });
      });
    }

  getDiscoveredPeripherals() {
      return new Promise<Peripheral[]>((fulfill, reject) => {
        bleManager.getDiscoveredPeripherals().then((result: Peripheral[] | null) => {
          if (result) {
            fulfill(result);
          } else {
            fulfill([]);
          }
        }).catch((error)=>{
          reject(error);
        });
      });
    }

  /**
   * [Android only]
   * @param peripheralId
   * @returns
   */
  removePeripheral(peripheralId: string) {
      return new Promise<void>((fulfill, reject) => {
        bleManager.removePeripheral(peripheralId).then(() => {
          fulfill();
        }).catch((error)=>{
          reject(error);
        });
      });
    }

  /**
   * @param peripheralId
   * @param serviceUUIDs [optional] not used on android, optional on ios.
   * @returns
   */
  isPeripheralConnected(peripheralId: string, serviceUUIDs: string[] = []) {
      return this.getConnectedPeripherals(serviceUUIDs).then((result) => {
        if (result.find((p) => p.id === peripheralId)) {
          return true;
        } else {
          return false;
        }
      });
    }

  /**
   * @param peripheralId
   * @param serviceUUIDs [optional] not used on android, optional on ios.
   * @returns
   */
  isScanning() {
      return new Promise<boolean>((fulfill, reject) => {
        bleManager.isScanning().then((status: boolean) => {
          fulfill(status);
        }).catch((error)=>{
          reject(error);
        });
      });
    }

  /**
   * [Android only, API 21+]
   * @param peripheralId
   * @param connectionPriority
   * @returns a promise that resolves with a boolean indicating of the connection priority was changed successfully, or rejects with an error message.
   */
  requestConnectionPriority(
      peripheralId: string,
      connectionPriority: ConnectionPriority
    ) {
      return new Promise<boolean>((fulfill, reject) => {
        bleManager.requestConnectionPriority(
          peripheralId,
          connectionPriority,
        ).then(() => {
          console.log('harmony is not support')
        });
      });
    }

  /**
   * [Android only, API 21+]
   * @param peripheralId
   * @param mtu size to be requested, in bytes.
   * @returns a promise resolving with the negotiated MTU if it succeeded. Beware that it might not be the one requested due to device's BLE limitations on both side of the negotiation.
   */
  requestMTU(peripheralId: string, mtu: number) {
      return new Promise<number>((fulfill, reject) => {
        bleManager.requestMTU(
          peripheralId,
          mtu
        ).then((mtu: number) => {
          fulfill(mtu);
        }).catch((error)=>{
          reject(error);
        });
      });
    }

  /**
   * [Android only, API 26+]
   *
   * @returns
   */
  getAssociatedPeripherals() {
      return new Promise<Peripheral[]>((fulfill, reject) => {
        bleManager.getAssociatedPeripherals().then(() => {
          console.log('harmony is not support')
        });
      });
    }

  /**
   * [Android only, API 26+]
   * @param peripheralId Peripheral to remove
   * @returns Promise that resolves once the peripheral has been removed. Rejects
   *          if no association is found.
   */
  removeAssociatedPeripheral(peripheralId: string) {
      return new Promise<void>((fulfill, reject) => {
        bleManager.removeAssociatedPeripheral(peripheralId).then(() => {
          console.log('harmony is not support')
        });
      });
    }

  /**
   * [Android only]
   *
   * Check if current device supports companion device manager.
   *
   * @return Promise resolving to a boolean.
   */
  supportsCompanion() {
      return new Promise<boolean>(fulfill => {
        bleManager.supportsCompanion().then(() => {
          console.log('harmony is not support')
        });
      });
    }

  /**
   * [Android only, API 26+]
   *
   * Start companion scan.
   */
  companionScan(
      serviceUUIDs: string[],
      options: CompanionScanOptions = {}
    ) {
      return new Promise<Peripheral | null>((fulfill, reject) => {
        bleManager.companionScan(serviceUUIDs, options).then(() => {
          console.log('harmony is not support')
        });
      });
    }

  /**
   * [Android only]
   * @param name
   */
  setName(name: string) {
      bleManager.setName('harmony is not support');
    }

  /**
   * [iOS only]
   * @param peripheralId
   * @returns
   */
  getMaximumWriteValueLengthForWithoutResponse(peripheralId: string) {
      return new Promise<number>((fulfill, reject) => {
        bleManager.getMaximumWriteValueLengthForWithoutResponse(
          peripheralId
        ).then(() => {
          console.log('harmony is not support')
        });
      });
    }

  /**
   * [iOS only]
   * @param peripheralId
   * @returns
   */
  getMaximumWriteValueLengthForWithResponse(peripheralId: string) {
      return new Promise<number>((fulfill, reject) => {
        bleManager.getMaximumWriteValueLengthForWithResponse(
          peripheralId
        ).then(() => {
          console.log('harmony is not support')
        });
      });
    }
}

export default new BleManager();
